﻿using System;
using System.Collections.Generic;
using AGB.Managerial;
using Zeta;
using Zeta.Common;
using Zeta.CommonBot;
using Zeta.Internals.Actors;
using Zeta.Internals.SNO;
using Zeta.TreeSharp;
using Action = Zeta.TreeSharp.Action;

namespace AGB.Modules.FrontalLobe_.CombatBehavior
{
    public class GenericCombat
    {
        private CerebralCortex _cortex = BotManager.CerebralCortex;
        private readonly CombatContext _context;
        private Composite _logic;
        private SNOPower _defaultAttack = SNOPower.Interact_Crouching;
        private SNOPower _defaultAOEAttack = SNOPower.Interact_Crouching;
        private SNOPower _defaultHeavyHitter = SNOPower.Interact_Crouching;
        private SNOPower _defaultDebuffer = SNOPower.Interact_Crouching;
        private SNOPower NullPower = SNOPower.Interact_Crouching;
        private int _debuffTimer = Environment.TickCount;
        private int _debuffTimeout = 6000;

        public void Reset()
        {
            _context.Reset();
        }

        public GenericCombat()
        {
            _context = new CombatContext(this);
        }

        public void SetPriorityTarget(String name)
        {
            _context.PriorityTargetName = name;
        }

        private static int AttackRange
        {
            get
            {
                switch (ZetaDia.Me.ActorClass)
                {
                    case ActorClass.Barbarian:
                        return 10;

                    case ActorClass.Monk:
                        return 10;

                    case ActorClass.Wizard:
                        return 50;

                    case ActorClass.DemonHunter:
                        return 50;

                    case ActorClass.WitchDoctor:
                        return 50;

                }
                return 10;
            }
        }


        public SNOPower DefaultAttack
        {
            get
            {
                if (_defaultAttack != NullPower) return _defaultAttack;
                
                switch (ZetaDia.Me.ActorClass)
                {
                    case ActorClass.Barbarian:
                        _defaultAttack = SNOPower.Barbarian_Bash;
                        return _defaultAttack;
                    case ActorClass.Monk:
                        _defaultAttack = SNOPower.Monk_FistsofThunder;
                        return _defaultAttack;
                    case ActorClass.Wizard:
                        _defaultAttack = SNOPower.Wizard_MagicMissile;
                        return _defaultAttack;
                    case ActorClass.DemonHunter:
                        _defaultAttack = SNOPower.DemonHunter_HungeringArrow;
                        return _defaultAttack;
                    case ActorClass.WitchDoctor:
                        _defaultAttack = SNOPower.Witchdoctor_PoisonDart;
                        return _defaultAttack;
                }
                return NullPower;
            }
        }

        public SNOPower DefaultAOEAttack
        {
            get
            {
                if (_defaultAOEAttack != NullPower) return _defaultAOEAttack;

                switch (ZetaDia.Me.ActorClass)
                {
                    case ActorClass.Barbarian:
                        _defaultAOEAttack = SNOPower.Barbarian_Earthquake;
                        return _defaultAOEAttack;
                    case ActorClass.Monk:
                        _defaultAOEAttack = SNOPower.Monk_FistsofThunder;
                        return _defaultAOEAttack;
                    case ActorClass.Wizard:
                        _defaultAOEAttack = SNOPower.Wizard_Blizzard;
                        return _defaultAOEAttack;
                    case ActorClass.DemonHunter:
                        _defaultAOEAttack = SNOPower.DemonHunter_ClusterArrow;
                        return _defaultAOEAttack;
                    case ActorClass.WitchDoctor:
                        _defaultAOEAttack = SNOPower.Witchdoctor_GraspOfTheDead;
                        return _defaultAOEAttack;
                }
                return NullPower;
            }
        }

        public virtual SNOPower HeavyHitter
        {
                        get
            {
                if (_defaultHeavyHitter != NullPower) return _defaultHeavyHitter;
                
                switch (ZetaDia.Me.ActorClass)
                {
                    case ActorClass.Barbarian:
                        _defaultHeavyHitter = SNOPower.Barbarian_Bash;
                        return _defaultHeavyHitter;
                    case ActorClass.Monk:
                        _defaultHeavyHitter = SNOPower.Monk_FistsofThunder;
                        return _defaultHeavyHitter;
                    case ActorClass.Wizard:
                        _defaultHeavyHitter = SNOPower.Wizard_MagicMissile;
                        return _defaultHeavyHitter;
                    case ActorClass.DemonHunter:
                        if (ZetaDia.Me.HotbarPowerIds.Contains(SNOPower.DemonHunter_Impale))
                            _defaultHeavyHitter = SNOPower.DemonHunter_Impale;
                        else _defaultHeavyHitter = SNOPower.DemonHunter_ClusterArrow;
                        return _defaultHeavyHitter;
                    case ActorClass.WitchDoctor:
                        _defaultHeavyHitter = SNOPower.Witchdoctor_PoisonDart;
                        return _defaultHeavyHitter;
                }
                return NullPower;
            }
        }

        public virtual SNOPower DefaultDebuff
        {
            get
            {
                if (_defaultDebuffer != NullPower) return _defaultDebuffer;

                switch (ZetaDia.Me.ActorClass)
                {
                    case ActorClass.Barbarian:
                        _defaultDebuffer = NullPower;
                        return _defaultDebuffer;
                    case ActorClass.Monk:
                        _defaultDebuffer = NullPower;
                        return _defaultDebuffer;
                    case ActorClass.Wizard:
                        _defaultDebuffer = NullPower;
                        return _defaultDebuffer;
                    case ActorClass.DemonHunter:
                        if (ZetaDia.Me.HotbarPowerIds.Contains(SNOPower.DemonHunter_MarkedForDeath))
                            _defaultDebuffer = SNOPower.DemonHunter_MarkedForDeath;
                        else _defaultDebuffer = NullPower;
                        return _defaultDebuffer;
                    case ActorClass.WitchDoctor:
                        _defaultHeavyHitter = NullPower;
                        return _defaultDebuffer;
                }
                return NullPower;
            }
        }

        public Composite GenerateCombatBehavior(float range)
        {
            _context.CombatRange = range;
            var combatBehavior = new PrioritySelector();
            combatBehavior.AddChild(GenerateCheckAvailableTargetsBehavior(range));
            combatBehavior.AddChild(new Decorator(TargetAvailable(), GenerateAttackBehavior()));
            combatBehavior.AddChild(GenerateMoveToTargetBehavior());
            return combatBehavior;
        }

        public Composite GenerateAttackBehavior()
        {
            Sequence AttackSequence = new Sequence();

            AttackSequence.AddChild(new Action((ActionSucceedDelegate)DetermineBestAttackAndTarget));
            AttackSequence.AddChild(new Action((ActionSucceedDelegate)ExecuteAttack));
            AttackSequence.AddChild(new Sleep(100));

            return AttackSequence;
        }

        public Composite GenerateCheckAvailableTargetsBehavior(float range)
        {
            var checkAvailableTargetsBehavior = new Action(
                delegate(object context)
                    {
                        if (_context.CurrentTarget == null) return RunStatus.Success;
                        if (_context.CurrentTarget.GetNativeObject() == null) return RunStatus.Success;
                        try
                        {
                            if (_context.CurrentTarget.getDistance() >= range) return RunStatus.Success;
                        }
                        catch (Exception e)
                        {
                            return RunStatus.Success;
                        }
                        _context.IsActive = true;
                        context = _context;
                       
                        return RunStatus.Failure;
                    });

            return checkAvailableTargetsBehavior;
        }

        public CanRunDecoratorDelegate TargetAvailable()
        {
            return delegate(object context)
                       {
                           if (_context.CurrentTarget == null) 
                               return false;
                           if (_context.CurrentTarget.GetNativeObject() == null)
                               return false;
                           if (_context.CurrentTarget.getDistance() > AttackRange || !_context.CurrentTarget.InLightOfSight())
                               return false;
                           return true;
                       };

        }

        public Composite GenerateMoveToTargetBehavior()
        {
            var Behavior = new PrioritySelector();
            var MoveToMob = new Action(delegate(object context)
                                           {
                                               if (_context.CurrentTarget == null) return;
                                               if (_context.CurrentTarget.GetNativeObject() == null) return;
                                               Vector3 position = _context.CurrentTarget.GetPosition();
                                               BotManager.Legs.Walk(position.X, position.Y);
                                           });
            Behavior.AddChild(MoveToMob);
            Behavior.AddChild(new Sleep(200));
            return Behavior;
        }

        private bool ShouldDebuff(object context)
        {
            if (_debuffTimer + _debuffTimeout > Environment.TickCount) return false;

            if (DefaultDebuff == NullPower) return false;

            if (_context.CurrentTarget != _context.PriorityTarget) return false;

            if (!PowerManager.CanCast(DefaultDebuff))
                return false;
            Logging.Write("Debuff using: "+DefaultDebuff);

            _context.NextAttack = DefaultDebuff;

            _debuffTimer = Environment.TickCount;

            _context.CurrentTarget = _context.PriorityTarget;
            return true;
        }

        private void DetermineBestAttackAndTarget(object context)
        {
            CachedUnit currentTarget = null;

            if (ShouldDebuff(context)) return;

            /*var canCastAOE = PowerManager.CanCast(DefaultAOEAttack);

            if (canCastAOE) currentTarget = CheckForClusters(_context.NearbyTargets);

            _context.NextAttack = DefaultAOEAttack;

            if (currentTarget != null)
            {
                _context.CurrentTarget = currentTarget;
                return;
            }*/

            if (_context.PriorityTarget != default(CachedUnit)) _context.CurrentTarget = _context.PriorityTarget;

            else _context.CurrentTarget = _context.NearestMob;

            var canCastAOE = PowerManager.CanCast(DefaultAOEAttack);

            _context.NextAttack = DefaultAOEAttack;

            if (canCastAOE && CheckForClusterSingle(_context.CurrentTarget))
            {
                return;
            }

            var canCastHeavyHitter = PowerManager.CanCast(HeavyHitter);

            _context.NextAttack = HeavyHitter;

            if (canCastHeavyHitter)
            {
                return;
            }

            _context.NextAttack = DefaultAttack;
        }

        private bool CheckForClusterSingle(CachedUnit target)
        {
            int genesisCluster = 0;

            foreach (var vesuva in _context.NearbyTargets)
            {
                float distance = _context.CurrentTarget.GetPosition().Distance2D(vesuva.GetPosition());
                if (distance < 10) genesisCluster++;
            }

            if (genesisCluster > 1) return true;
            return false;
        }

        /*private CachedUnit CheckForClusters(List<CachedUnit>  targets)
        {
            int biggestCluster = 2;
            CachedUnit target = null;

            if (_context.PriorityTarget != default(CachedUnit))
            {
                int genesisCluster = 0;
                foreach (var vesuva in targets)
                {
                    float distance = _context.PriorityTarget.GetPosition().Distance2D(vesuva.GetPosition());
                    if (distance < 20) genesisCluster++;
                }
                if (genesisCluster > 1) return _context.PriorityTarget;
                return null;
            }

            foreach (var genesis in targets)
            {
                int genesisCluster = 0;
                foreach (var vesuva in targets)
                {                  
                    float distance = genesis.GetPosition().Distance2D(vesuva.GetPosition());
                    if (distance < 20) genesisCluster++;
                }
                if (genesisCluster > biggestCluster)
                {
                    biggestCluster = genesisCluster;
                    target = genesis;
                }
            }
            return target;
        }*/

        private void ExecuteAttack(object context)
        {
            if (_context.CurrentTarget == null)
            {
                Logging.WriteVerbose("Error Attacking: Invalid_Unit will try again !");
                _context.Reset();
                return;
            }
            if (_context.CurrentTarget.GetNativeObject() == null)
            {
                Logging.WriteVerbose("Error Attacking: " + _context.CurrentTarget.Name+" will try again !");
                _context.Reset();
                return;
            }
            var pos = _context.CurrentTarget.GetPosition();
            if (pos.X  < 0 && pos.Y < 0 && pos.Z < 0)
            {
                Logging.WriteVerbose("Error Attacking: " + _context.CurrentTarget.Name + " will try again (InvalidPosition) !");
                _context.Reset();
                return;
            }
            ZetaDia.Me.UsePower(_context.NextAttack,
                                _context.CurrentTarget.GetPosition(), ZetaDia.Me.WorldDynamicId, _context.CurrentTarget.ACDGuid);
            _context.Reset();
        }
    }
}

